#-*- coding: utf-8 -*-

__all__ = ['IDatum']

from traits.api import Interface, Unicode, Type, Tuple

class IDatum(Interface):
  u"""
  The Datum class provides a generic container for simple python types.

  Datum provides :

    - meta-informations: group, alias, tooltip
    - type information: nature
    - default and available values
    - auto casting
    
  .. info :: 
  
    All examples use 'Datum' class to illustrate expected behaviour.
    Datum is one example of full implementation of IDatum.


  Attribute restrictions :
  
  An IDatum implementation must ensure these attribute restriction are respected : 
  
    - name, alias, tooltip, group must be **unicode** or **None**
    - nature : must be a nature official supported by this implementation or None.
      for example, if only "bool" and "int" are officially supported,
      Datum(u'd', str, 'a') must raise error.
      If user changes nature, value, default_value and value_list must be 
      converted automatically to new nature.
    - value, default_value must respect nature.
      You should allow user to pass value of different types easy to convert.
      For example, nature=float should allow 1 (int), 1.0 (float) and '1' (str)
    - value_list : 

      * must be a list and all elements of this list must respect 
        value/default_value restrictions.

      * cannot be edited directly. User must use addValue, removeValue, etc.
      
    - changing value or default_value must respect ModeAutoUpdate/ModeDoNotUpdate

  .. warning ::
  
    See datum specification for more details
    

  """

  ordered_keys = Tuple
  """ ordered tuple of available fields"""

  ModeAutoUpdate = u'AutoUpdate'
  """ Update automatically value_list when value or default_value changes"""
  ModeDoNotUpdate = u'DoNotUpdate'
  """ Never update value_list automatically """

  def __init__(self, name=None, nature=None, value=None, default_value=None,
               value_list=None, tooltip=None, group=None, alias=None, *t, **d):
    u"""
    *t and **d are important to allow datum extensions
    """

  def connect(self, signal, method):
    u"""
    Connects signal to given method.

    You must support these signals :
      
      - 'valueChanged'; args: self, old_value, new_value
      - 'defaultValueChanged'; args: self, old_value, new_value
      - 'valueListChanged'; args: self, new_value_list
      - 'natureChanged'; args: self, old_value, new_value
      - 'toolTipChanged'; args: self, old_value, new_value
      
    This signals are emitted when corresponding field changes.

    Only one connection between a given signal and a given method is allowed.
    datum.connect('sig1', method_a) called one, two or n times has same effect.

    Example :
    
    >>> from pyLot.core import Datum
    >>> def print_value(datum, old, new):
    ...   '''
    ...   Displays datum changes
    ...   '''
    ...   print 'value of %r changes from %r to %r' %(datum.name, old, new)

    >>> a =Datum(u'a', int, 1, alias=u'Datum A')
    >>> a.connect('valueChanged', print_value)
      
    >>> a.value = 3
    value of u'a' changes from 1 to 3

    """

  def disconnect(self, signal, method):
    u"""
    Break connection between signal and method
    """

  def clear(self):
    u"""
    Reset all attributes to None.
    """

  def update(self, d, mod_attrs=None):
    """
    Updates datum from dict or Datum 'd'. 
    Modifies only fields specified in mod_attrs.

    :param d: a field=value dict
    :type d: :obj:`dict`
    :param mod_attrs: tuple of attributes (fields) you want to modify
    :type mod_attrs: :obj:`tuple`
    """
    pass

  def userUpgrade(self, config_option):
    u"""
    Like update but updates only value, default_value and value_list

    >>> from pyLot.core import Datum
    >>> d = Datum(u'd', int, 1, alias=u'd')
    >>> up = Datum(u'd', unicode, u'3', alias=u'tmp')
    >>> d.userUpgrade(up)
    >>> d
    Datum(u'd', int, 3, None, [1, 3], None, None, u'd')
    """
    pass

  @classmethod
  def staticConvert(cls, val, nat):
    u"""
    Converts val to nat
    >>> from pyLot.core import Datum
    >>> Datum.staticConvert(u'1', int)
    1
    """

  def convert(self, value, nature):
    u"""
    Call casting methods.
    
    Instance version of staticConvert (generally a simple alias)
    """

  def toDict (self, mode=u'light'):
    u"""
    Returns a dict representation of current Datum.

    >>> from pyLot.core import Datum
    >>> d = Datum(u'd', int, 1)
    >>> d.toDict()
    {u'value_list': [1], u'name': u'd', u'value': 1, u'nature': <type 'int'>}
    """

  def toList (self):
    u"""
    Returns a list representation of current Datum

    >>> from pyLot.core import Datum
    >>> d = Datum(u'a', int, 1)
    >>> d.toList()
    [u'a', <type 'int'>, 1, None, [1], None, None, None]
    """

  def listToDict (self, src, mode=u'light'):
    u"""
    Transforms an ordered list (according to ordered_keys order) to dictionary
    If l is None, return a dict representation of Datum

    >>> d = Datum()
    >>> print d.listToDict([u'AAA', int, 1, 2,[1,4,67]])
    {u'default_value': 2, u'value_list': [1, 4, 67], u'name': u'AAA', u'value': 1, u'nature': <type 'int'>}

    :param l: attributes' list or None
    :type l: :obj:`list` or :obj:`None`
    """
    dic = {}
    for i, attr in enumerate(self.ordered_keys):
      try: val = src[i]
      except IndexError :
        break #yen a pas assez dans la liste
      else:
        if val is not None or val is None and mode == u'full'  :
          dic[attr] = val
    return dic

  def upValueList (self, value_list):
    u"""
    updates Datum.value_list with value_list
    """

  def removeValue(self, value):
    u"""
    removes value from list
    """

  def addValue(self, value):
    u"""
    Adds value to Datum.value_list
    """

  def setUpdateMode(self, mode):
    u"""
    Changes update mode
    """

  def valueToXmlElement (self, value, role=None):
    u"""
    Returns a pyLot.core.etree.Element (cElementTree or ElementTree Element object)
    tag: u'value'
    
    role: 
      - u'default' (corresponds to default_value)
      - u'current' (corresponds to value)
      - None (a value of value_list)
    
    ex : d.valueToXmlElement (1, role=u'current') return an Element corresponding to
    <value role='current'>1</value>
    d.valueToXmlElement (1)
    <value>1</value>
    """

  def __unicode__(self):
    u"""
    unicode representation of IDatum :
    u'Datum(name, nature, value)'
    """

  def __str__ (self):
    u"""
    str representation of IDatum :
    'Datum(name, nature, value)'
    """

  def __repr__(self):
    u"""
    Full representation of IDatum :
    'Datum(name, nature, value, default_value, ...)'
    """

  def __nonzero__(self):
    u"""
    A datum is considered False if one of name or nature isn't defined.
    "Not defined" doesn't mean "False"

    >>> bool(Datum())
    False
    >>> bool(Datum(None))
    False
    >>> bool(Datum(u'obj', None))
    False

    Though bool(0) or bool('') are False, the Datum is considered non zero
    because 0 or "" may be wanted values

    >>> bool(Datum(u'obj', value=0))
    True
    
    This is True due to constructor behaviour :
    >>> Datum(u'obj', value=0) == Datum(u'obj', int, 0)
    True
    """
    return not ((self._name is None) or (self._nature is None))

  def __eq__ (self, other):
    u"""
    Two IDatum are equals if all fields are equals
    >>> d1 = Datum(u'a', int, 1, 0, [3,4], u'tt', u'gr', u'al')
    >>> d2 = Datum(u'a', int, 1, 0, [3,4], u'tt', u'gr', u'al')

    >>> d1 == d2
    True
    >>> d1.group = u'g'
    >>> d1 == d2
    False
    """

  def __ne__ (self, other):
    return not self.__eq__(other)

  def __deepcopy__ (self, memo=None, _nil=[]):
    u"""
    >>> d = Datum(u'a', int, 1, 0, [3,4], u'tt', u'gr', u'al')
    >>> c = d
    >>> print c == d , c is d
    True True
    >>> import copy
    >>> c = copy.deepcopy(d)
    >>> print c == d , c is d
    True False
    >>> c.value_list = [888, 999]
    >>> c.value_list ; d.value_list
    [888, 999]
    [0, 1, 3, 4]
    """

  def copy(self):
    u"""
    __deepcopy__ alias
    """

  @classmethod
  def getFieldsAll (cls):
    u"""
    Return datum fields
    """

  @classmethod
  def getFieldsStd (cls):
    u"""
    All fields but name and nature
    """

  @classmethod
  def getFieldsDesc (cls):
    u"""
    All meta-information fields : tooltip, group, alias
    """

